package it.omnisong.server.index;

import it.omnisong.client.model.Song;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryTermVector;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.apache.lucene.search.BooleanClause;
import org.junit.Test;

public class LyricSearcher {
	
	private IndexSearcher searcher;
	private Analyzer analyzer;
	TopScoreDocCollector collector = null;
	private ArrayList<Song> resultDocs = null;
	private String indexDir = "omnisonggwt/index/lyrics";
	
	/* costruttore no-argument */
	public LyricSearcher() throws IOException {
		FSDirectory dir = FSDirectory.open(new File(this.indexDir));
		this.searcher = new IndexSearcher(dir, true);
		this.analyzer = new StandardAnalyzer(Version.LUCENE_30);
	}
	
	/**
	 * @param indexDir the indexDir to set
	 */
	public void setIndexDir(String indexDir) {
		this.indexDir = indexDir;
	}
	
	/* metodo che ricerca nel campo 'artist' dell'indice, con la query passata come parametro */
	public ArrayList<Song> searchSongArtist(String queryString) throws ParseException, IOException {
		
		/* creo il collector dei risultati, prende i migliori 5 */
		this.collector = TopScoreDocCollector.create(5, true);
		/* creo la lista che contiene i risultati */
		this.resultDocs = new ArrayList<Song>();
		
		this.resultDocs = new ArrayList<Song>();
	    QueryParser parser = new QueryParser(Version.LUCENE_30, "artist", analyzer);
	    parser.setDefaultOperator(QueryParser.Operator.AND); // setto l'operatore tra termini
	    parser.setFuzzyMinSim((float) 0.8); // setto la minima distanza di similarit�
	    queryString = fuzzyQuery(queryString); // modifico la query per ottenere una ricerca fuzzy
	    Query query = parser.parse(queryString);
	    this.searcher.search(query, collector);
	    ScoreDoc[] hits = collector.topDocs().scoreDocs;
	    // Iterate through the results:
	    for (int i = 0; i < hits.length; i++) {
	      Document hitDoc = this.searcher.doc(hits[i].doc);
	      String title = hitDoc.get("title");
	      String artist = hitDoc.get("artist");
	      String lyric = hitDoc.get("lyric");
	      this.resultDocs.add(new Song(title, artist, lyric));
	    }
	    this.closeSearcher();
	    this.collector = null;
	    return this.resultDocs;
	}
	
	/* metodo che ricerca nel campo 'title' dell'indice, con la query passata come parametro */
	public ArrayList<Song> searchSongTitle(String queryString) throws ParseException, IOException {
		
		/* creo il collector dei risultati, prende i migliori 5 */
		this.collector = TopScoreDocCollector.create(5, true);
		/* creo la lista che contiene i risultati */
		this.resultDocs = new ArrayList<Song>();
		
		this.resultDocs = new ArrayList<Song>();
	    QueryParser parser = new QueryParser(Version.LUCENE_30, "title", analyzer);
	    parser.setDefaultOperator(QueryParser.Operator.AND); // setto l'operatore tra termini
	    parser.setFuzzyMinSim((float) 0.8); // setto la minima distanza di similarit�
	    queryString = fuzzyQuery(queryString); // modifico la query per ottenere una ricerca fuzzy
	    Query query = parser.parse(queryString);
	    this.searcher.search(query, collector);
	    ScoreDoc[] hits = collector.topDocs().scoreDocs;
	    // Iterate through the results:
	    for (int i = 0; i < hits.length; i++) {
	      Document hitDoc = this.searcher.doc(hits[i].doc);
	      String title = hitDoc.get("title");
	      String artist = hitDoc.get("artist");
	      String lyric = hitDoc.get("lyric");
	      this.resultDocs.add(new Song(title, artist, lyric));
	    }
	    this.closeSearcher();
	    this.collector = null;
	    return this.resultDocs;
	}
	
	/* metodo che ricerca nel campo 'lyric' dell'indice, con la query passata come parametro */
	public ArrayList<Song> searchSongLyric(String queryString) throws ParseException, IOException {
		
		/* creo il collector dei risultati, prende i migliori 5 */
		this.collector = TopScoreDocCollector.create(5, true);
		/* creo la lista che contiene i risultati */
		this.resultDocs = new ArrayList<Song>();
		
	    QueryParser parser = new QueryParser(Version.LUCENE_30, "lyric", analyzer);
	    parser.setDefaultOperator(QueryParser.Operator.AND); // setto l'operatore tra termini
	    parser.setFuzzyMinSim((float) 0.8); // setto la minima distanza di similarit�
	    queryString = fuzzyQuery(queryString); // modifico la query per ottenere una ricerca fuzzy
	    Query query = parser.parse(queryString);
	    this.searcher.search(query, collector);
	    ScoreDoc[] hits = collector.topDocs().scoreDocs;
	    // Iterate through the results:
	    for (int i = 0; i < hits.length; i++) {
	      Document hitDoc = this.searcher.doc(hits[i].doc);
	      String title = hitDoc.get("title");
	      String artist = hitDoc.get("artist");
	      String lyric = hitDoc.get("lyric");
	      this.resultDocs.add(new Song(title, artist, lyric));
	    }
	    this.closeSearcher();
	    this.collector = null;
	    return this.resultDocs;
	}
	
	/* ricerca avanzata del testo di una canzone, specificando anche l'artista o il titolo 
	 * l'input � un array di stringhe, la prima stringa rappresenta il campo artista, la seconda
	 * il titolo e la terza la lyric */
	public ArrayList<Song> advancedLyricSearch(String[] query) throws ParseException, IOException {

		/* creo il collector dei risultati, prende i migliori 5 */
		this.collector = TopScoreDocCollector.create(5, true);
		/* creo la lista che contiene i risultati */
		this.resultDocs = new ArrayList<Song>();
		
		String artistQueryString = this.fuzzyQuery(query[0]);
		String titleQueryString = this.fuzzyQuery(query[1]);
		String lyricQueryString = this.fuzzyQuery(query[2]);
		
		/* imposto i parser per le 3 stringhe */
		QueryParser artistParser = new QueryParser(Version.LUCENE_30, "artist", analyzer);
		QueryParser titleParser = new QueryParser(Version.LUCENE_30, "title", analyzer);
		QueryParser lyricParser = new QueryParser(Version.LUCENE_30, "lyric", analyzer);
	    //parser.setFuzzyMinSim((float) 0.9); servirebbe a settare il grado di similarit� (distanza di levenstein), ma devo vedere ancora coem funziona
		
		/* imposto and come operatore */
	    artistParser.setDefaultOperator(QueryParser.Operator.AND);
	    titleParser.setDefaultOperator(QueryParser.Operator.AND);
	    lyricParser.setDefaultOperator(QueryParser.Operator.AND);
	    
	    /* imposto la similarity nelle query */
	    artistParser.setFuzzyMinSim((float) 0.8);
	    titleParser.setFuzzyMinSim((float) 0.8);
	    lyricParser.setFuzzyMinSim((float) 0.8);
	    
	    /* ottengo delle 'field query' dai parser */
	    Query artistQuery = artistParser.parse(artistQueryString);
	    Query titleQuery = titleParser.parse(titleQueryString);
	    Query lyricQuery = lyricParser.parse(lyricQueryString);
		
	    /* creo una query booleana e gli aggiungo le query costruite fin'ora, controllo
	     * se ci sono campi della form vuoti */
		BooleanQuery completeQuery = new BooleanQuery();
		if(!artistQueryString.equals(""))
			completeQuery.add(artistQuery, BooleanClause.Occur.MUST);
		if(!titleQueryString.equals(""))
			completeQuery.add(titleQuery, BooleanClause.Occur.MUST);
		if(!lyricQueryString.equals(""))
			completeQuery.add(lyricQuery, BooleanClause.Occur.MUST);
		
		/* effettuo la ricerca */
		this.searcher.search(completeQuery, collector);
	    ScoreDoc[] hits = collector.topDocs().scoreDocs;
	    // Iterate through the results:
	    for (int i = 0; i < hits.length; i++) {
	      Document hitDoc = this.searcher.doc(hits[i].doc);
	      String title = hitDoc.get("title");
	      String artist = hitDoc.get("artist");
	      String lyric = hitDoc.get("lyric");
	      this.resultDocs.add(new Song(title, artist, lyric));
	    }
	    this.closeSearcher();    

		System.out.println(completeQuery.toString());
		
	    return this.resultDocs;
				
}
	
	/* metodo che modifica la stringa di query per crearne una fuzzy*/
	private String fuzzyQuery(String queryString) {
		
	    QueryTermVector termVector = new QueryTermVector(queryString, this.analyzer); // prendo il vettore dei termini della query, ottenuti con lo stesso analizzatore
	    String[] terms = termVector.getTerms();
	    String queryResult = "";
	    for(String term : terms) {
	    	term = term.concat("~");
	    	queryResult = queryResult.concat(term+" ");
	    }
	    queryResult = queryResult.substring(0, queryResult.length()-1); // trucco per togliere lo spazio finale (probabilmente neanche serve)
		return queryResult;
		
	}
	
	/* metodo che chiude il searcher */
	public void closeSearcher() throws IOException {
	    this.searcher.close();
	}

	/* metodo che ritorna i risultati delle ricerche sui 3 campi --> l'ho separato in 3 metodi */
	public List<LinkedList<Document>> generalQuery(String query, String[] args){
		List<LinkedList<Document>> res_set = new LinkedList<LinkedList<Document>>();
		
		LinkedList<Document> res=this.queryForKeywords(query,"lyric", args);
		LinkedList<Document> resByTitle=this.queryForKeywords(query, "title", args);
		LinkedList<Document> resByArtist=this.queryForKeywords(query, "artist", args);
		
		res_set.add(res);
		res_set.add(resByTitle);
		res_set.add(resByArtist);
		
		return res_set;
	}
	
	/* main per testare il vecchio metodo */
	public static void main(String[] args){
		
		LyricSearcher dbq = null;
		try {
			dbq = new LyricSearcher();
			dbq.setIndexDir("war/omnisonggwt/index/lyrics");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		String query="10000~";
		LinkedList<Document> res=dbq.queryForKeywords(query,"lyric", args);
		LinkedList<Document> resByTitle=dbq.queryForKeywords(query, "title", args);
		LinkedList<Document> resByArtist=dbq.queryForKeywords(query, "artist", args);
		
		System.out.println("Results by Lyric:");
		for (Document d : res){
			System.out.println(". " + d.get("title")+"="+d.get("lyric"));
		}
		
		System.out.println("Results by Title:");
		for (Document d : resByTitle){
			System.out.println(". " + d.get("title")+"="+d.get("artist"));
		}
		
		System.out.println("Results by Artist:");
		for (Document d : resByArtist){
			System.out.println(". " + d.get("title")+"="+d.get("artist"));
		}
	}
	
	public LinkedList<Document> queryForKeywords(String keywords, 
			String field, String[] args){
		StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
		
		String querystr = args.length > 0 ? args[0] : keywords;
		
		LinkedList<Document> results=new LinkedList<Document>();
		
		try{
			Query q = new QueryParser(
				    Version.LUCENE_30, field, analyzer).parse(querystr);
			    	
			Directory a=FSDirectory.open(new File("war/omnisonggwt/index/lyrics"));
		
			// ricerca	
			int hitsPerPage = 10;
		    IndexSearcher searcher = new IndexSearcher(a, true);
		    TopScoreDocCollector collector = 
		    TopScoreDocCollector.create(hitsPerPage, true);
		    searcher.search(q, collector);
		    ScoreDoc[] hits = collector.topDocs().scoreDocs;
		    
		    System.out.println("Found: "+hits.length);
		    
		    for (int i=0; i<hits.length;++i){
		    	int docId = hits[i].doc;
			    Document d = searcher.doc(docId);
			    
			    results.add(d);
			    
		    }
			
			searcher.close();
		}catch(Exception e){
			e.printStackTrace();
		}

		return results;
	}
	
	@Test
	public void Test() {
		
		//System.out.println(this.fuzzyQuery("the animal of big"));
		
		/* testo l'advanced search 
		String artist = "pino";
		String title = "guerra";
		String lyric = "� andato in guerra";
		String[] query = new String[3];
		query[0] = artist;
		query[1] = title;
		query[2] = lyric;
		try {
			this.advancedLyricSearch(query);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 fine test advanced search */ 
		
		/* testo la search per title */
		ArrayList<Song> songs = null;
		try {
			songs = this.searchSongTitle("love");
			for(Song song : songs)
				System.out.println(song.getTitle());
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}

}
